<?php
namespace CFPropertyList;
use \DOMDocument,\Iterator,\ArrayAccess;
abstract class CFType 
{
	protected $value = null;
	public function __construct($value=null) 
	{
		$this->setValue($value);
	}
	public function getValue() 
	{
		return $this->value;
	}
	public function setValue($value) 
	{
		$this->value = $value;
	}
	public function toXML(DOMDocument $doc,$nodeName) 
	{
		$node = $doc->createElement($nodeName);
		$text = $doc->createTextNode($this->value);
		$node->appendChild($text);
		return $node;
	}
	public abstract function toBinary(CFBinaryPropertyList &$bplist);
	public function toArray() 
	{
		return $this->getValue();
	}
}
class CFString extends CFType 
{
	public function toXML(DOMDocument $doc,$nodeName="") 
	{
		return parent::toXML($doc,'string');
	}
	public function toBinary(CFBinaryPropertyList &$bplist) 
	{
		return $bplist->stringToBinary($this->value);
	}
}
class CFUid extends CFType 
{
	public function toXML(DOMDocument $doc,$nodeName="") 
	{
		$obj = new CFDictionary(array('CF$UID'=>new CFNumber($this->value)));
		return $obj->toXml($doc);
	}
	public function toBinary(CFBinaryPropertyList &$bplist) 
	{
		return $bplist->uidToBinary($this->value);
	}
}
class CFNumber extends CFType 
{
	public function toXML(DOMDocument $doc,$nodeName="") 
	{
		$ret = 'real';
		if(intval($this->value) == $this->value &&!is_float($this->value) &&strpos($this->value,'.') === false) 
		{
			$this->value = intval($this->value);
			$ret = 'integer';
		}
		return parent::toXML($doc,$ret);
	}
	public function toBinary(CFBinaryPropertyList &$bplist) 
	{
		return $bplist->numToBinary($this->value);
	}
}
class CFDate extends CFType 
{
	const TIMESTAMP_APPLE = 0;
	const TIMESTAMP_UNIX = 1;
	const DATE_DIFF_APPLE_UNIX = 978307200;
	function __construct($value,$format=CFDate::TIMESTAMP_UNIX) 
	{
		$this->setValue($value,$format);
	}
	function setValue($value,$format=CFDate::TIMESTAMP_UNIX) 
	{
		if($format == CFDate::TIMESTAMP_UNIX) $this->value = $value;
		else $this->value = $value +CFDate::DATE_DIFF_APPLE_UNIX;
	}
	function getValue($format=CFDate::TIMESTAMP_UNIX) 
	{
		if($format == CFDate::TIMESTAMP_UNIX) return $this->value;
		else return $this->value -CFDate::DATE_DIFF_APPLE_UNIX;
	}
	public function toXML(DOMDocument $doc,$nodeName="") 
	{
		$text = $doc->createTextNode(gmdate("Y-m-d\TH:i:s\Z",$this->getValue()));
		$node = $doc->createElement("date");
		$node->appendChild($text);
		return $node;
	}
	public function toBinary(CFBinaryPropertyList &$bplist) 
	{
		return $bplist->dateToBinary($this->value);
	}
	public static function dateValue($val) 
	{
		if(!preg_match('/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z/',$val,$matches)) throw new PListException("Unknown date format: $val");
		return gmmktime($matches[4],$matches[5],$matches[6],$matches[2],$matches[3],$matches[1]);
	}
}
class CFBoolean extends CFType 
{
	public function toXML(DOMDocument $doc,$nodeName="") 
	{
		return $doc->createElement($this->value ?'true': 'false');
	}
	public function toBinary(CFBinaryPropertyList &$bplist) 
	{
		return $bplist->boolToBinary($this->value);
	}
}
class CFData extends CFType 
{
	public function __construct($value=null,$already_coded=false) 
	{
		if($already_coded) $this->value = $value;
		else $this->setValue($value);
	}
	public function setValue($value) 
	{
		$this->value = base64_encode($value);
	}
	public function getCodedValue() 
	{
		return $this->value;
	}
	public function getValue() 
	{
		return base64_decode($this->value);
	}
	public function toXML(DOMDocument $doc,$nodeName="") 
	{
		return parent::toXML($doc,'data');
	}
	public function toBinary(CFBinaryPropertyList &$bplist) 
	{
		return $bplist->dataToBinary($this->getValue());
	}
}
class CFArray extends CFType implements Iterator,ArrayAccess 
{
	protected $iteratorPosition = 0;
	public function __construct($value=array()) 
	{
		$this->value = $value;
	}
	public function setValue($value) 
	{
	}
	public function add(CFType $value=null) 
	{
		if( !$value ) $value = new CFString();
		$this->value[] = $value;
	}
	public function get($key) 
	{
		if(isset($this->value[$key])) return $this->value[$key];
		return null;
	}
	public function del($key) 
	{
		if(isset($this->value[$key])) unset($this->value[$key]);
	}
	public function toXML(DOMDocument $doc,$nodeName="") 
	{
		$node = $doc->createElement('array');
		foreach($this->value as $value) $node->appendChild($value->toXML($doc));
		return $node;
	}
	public function toBinary(CFBinaryPropertyList &$bplist) 
	{
		return $bplist->arrayToBinary($this);
	}
	public function toArray() 
	{
		$a = array();
		foreach($this->value as $value) $a[] = $value->toArray();
		return $a;
	}
	public function rewind() 
	{
		$this->iteratorPosition = 0;
	}
	public function current() 
	{
		return $this->value[$this->iteratorPosition];
	}
	public function key() 
	{
		return $this->iteratorPosition;
	}
	public function next() 
	{
		$this->iteratorPosition++;
	}
	public function valid() 
	{
		return isset($this->value[$this->iteratorPosition]);
	}
	public function offsetExists($key) 
	{
		return isset($this->value[$key]);
	}
	public function offsetGet($key) 
	{
		return $this->get($key);
	}
	public function offsetSet($key,$value) 
	{
		return $this->setValue($value);
	}
	public function offsetUnset($key) 
	{
	}
}
class CFDictionary extends CFType implements Iterator 
{
	protected $iteratorPosition = 0;
	protected $iteratorKeys = null;
	public function __construct($value=array()) 
	{
		$this->value = $value;
	}
	public function setValue($value) 
	{
	}
	public function add($key,CFType $value=null) 
	{
		if( !$value ) $value = new CFString();
		$this->value[$key] = $value;
	}
	public function get($key) 
	{
		if(isset($this->value[$key])) return $this->value[$key];
		return null;
	}
	public function __get($key) 
	{
		return $this->get($key);
	}
	public function del($key) 
	{
		if(isset($this->value[$key])) unset($this->value[$key]);
	}
	public function toXML(DOMDocument $doc,$nodeName="") 
	{
		$node = $doc->createElement('dict');
		foreach($this->value as $key =>$value) 
		{
			$node->appendChild($doc->createElement('key',$key));
			$node->appendChild($value->toXML($doc));
		}
		return $node;
	}
	public function toBinary(CFBinaryPropertyList &$bplist) 
	{
		return $bplist->dictToBinary($this);
	}
	public function toArray() 
	{
		$a = array();
		foreach($this->value as $key =>$value) $a[$key] = $value->toArray();
		return $a;
	}
	public function rewind() 
	{
		$this->iteratorPosition = 0;
		$this->iteratorKeys = array_keys($this->value);
	}
	public function current() 
	{
		return $this->value[$this->iteratorKeys[$this->iteratorPosition]];
	}
	public function key() 
	{
		return $this->iteratorKeys[$this->iteratorPosition];
	}
	public function next() 
	{
		$this->iteratorPosition++;
	}
	public function valid() 
	{
		return isset($this->iteratorKeys[$this->iteratorPosition]) &&isset($this->value[$this->iteratorKeys[$this->iteratorPosition]]);
	}
}
?>